package gameoflive;
import java.awt.Color;
import java.awt.Graphics2D;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Random;


public class GameOfLive extends AbstractRunnable implements IRenderer
{
    private static GameOfLive instance;
    
    public synchronized static GameOfLive getInstance()
    {
        if (instance == null)
        {
            instance = new GameOfLive();
        }
        return instance;
    }
    
    private Point           maxXY;
    
    public Point getMaxXY()
    {
        return maxXY;
    }
    
    private List<Cell>      cells;
    
    private int             generation;
    private int             livingCells          = 0;
    private int             livingCellsIdentical = 0;
    
    private GameOfLiveRules rules                = GameOfLiveRules.DEFAULT;
    
    public void setRule(String rule)
    {
        try
        {
            GameOfLiveRules newRule = new GameOfLiveRules(rule);
            pause();
            rules = newRule;
            resume();
        }
        catch(Exception ex)
        {
            Game.log(ex);
        }
    }
    
    // Lebende Zellen generieren
    private static Random RANDOM = new Random(System.currentTimeMillis());

    public Cell getCell(Point position)
    {
        return getCell((int) position.getWidth(), (int) position.getHeight());
    }
    
    public Cell getCell(int x, int y)
    {
        if (x < 0 || x > maxXY.getWidth())
        {
            throw new IndexOutOfBoundsException(x + " not in 0-" + maxXY.getWidth());
        }
        if (y < 0 || y > maxXY.getHeight())
        {
            throw new IndexOutOfBoundsException(y + " not in 0-" + maxXY.getHeight());
        }
        int index = calculateIndex(x, y);
        if (index == cells.size())
        {
            cells.add(new Cell(x, y, false, false));
        }
        return cells.get(index);
    }
    
    private int calculateIndex(int x, int y)
    {
        int index = 0;
        index = x + y * maxXY.getWidth();
        return index;
    }
    
    private void initCells()
    {
        this.cells = new ArrayList<Cell>();
    }
    
    public boolean prepareNextGeneration()
    {
        generation++;
        int alive = 0;
        for (Iterator<Cell> i = cells.iterator(); i.hasNext();)
        {
            Cell cell = i.next();
            int livingNeighbors = cell.countLivingNeighbors();
            cell.setSurviving(rules.isSurviving(cell.isAlive(), livingNeighbors));
            if (cell.isAlive())
            {
                alive++;
            }
        }
        boolean prepareNextGeneration = true;
        if (alive == livingCells)
        {
            livingCellsIdentical++;
        }
        else
        {
            livingCellsIdentical = 0;
        }
        livingCells = alive;
        setStatus("Generation " + generation + "; " + livingCells + " Living Cells");
        if (livingCells <= 0)
        {
            Game.log("No more living cells");
            prepareNextGeneration = false;
        }
        else if (livingCellsIdentical > 50)
        {
            prepareNextGeneration = false;
        }
        return prepareNextGeneration;
    }
    
    private void addLivingCells()
    {
        addLivingCells(RANDOM.nextInt(maxXY.getX() * maxXY.getY()));
    }
    
    private void addLivingCells(int count)
    {
        if (isRunning() && isPaused() == false)
        {
            pause();
        }
        List<Point> livingCells = new ArrayList<Point>();
        do
        {
            Point position = new Point(RANDOM.nextInt((int) (maxXY.getWidth() + 1)), 
                                       RANDOM.nextInt((int) (maxXY.getHeight() + 1)));
            if (livingCells.contains(position) == false)
            {
                livingCells.add(position);
            }
        }
        while (livingCells.size() < count);
        // reset count
        count = 0;
        for (Cell cell : cells)
        {
            if (livingCells.contains(cell.getPosition()))
            {
                cell.setAlive(true);
                cell.setSurviving(true);
            }
        }
        Game.log(count + " living cells added");
        resume();
    }
    
    private void resetGeneration()
    {
        pause();
        for (Cell cell : cells)
        {
            cell.setAlive(false);
            cell.setSurviving(false);
        }
        generation = 0;
    }
    
    public void restart()
    {
        resetGeneration();
        addLivingCells();
    }
    
    public void initGeneration(int x, int y)
    {
        // Anzahl Zellen
        if (isRunning() || isPaused())
        {
            stop();
        }
        maxXY = new Point(x, y);
        int count = maxXY.getX() * maxXY.getY();
        // Liste generieren
        initCells();
        for (y = 0; y < maxXY.getHeight(); y++)
        {
            for (x = 0; x < maxXY.getWidth(); x++)
            {
                getCell(x, y);
            }
        }
        Game.log("buildMap() complete, " + cells.size() + " cells created");
        addLivingCells(RANDOM.nextInt(count));
        generation = 0;
        start();
    }
    
    @Override
    public void render(Graphics2D g, int cellSize)
    {
        if (isRunning())
        {
            for (Iterator<Cell> i = cells.iterator(); i.hasNext();)
            {
                Cell cell = i.next();
                Point position = cell.getPosition();
                if (cell.isAlive())
                {
                    if (cell.isSurviving())
                    {
                        g.setColor(Color.GREEN);
                    }
                    else
                    {
                        g.setColor(Color.RED);
                    }
                }
                else
                {
                    if (cell.isSurviving())
                    {
                        g.setColor(Color.DARK_GRAY);
                    }
                    else
                    {
                        g.setColor(Color.BLACK);
                    }
                }
                g.fillRect((int) position.getWidth() * cellSize, (int) position.getHeight() * cellSize, cellSize, cellSize);
                if (isPaused() == false)
                {
                    // next round
                    cell.setAlive(cell.isSurviving());
                }
            }
        }
    }
}
